/************************************************************************************************** * Copyright (C) 2010 Sense Observation Systems, Rotterdam, the Netherlands. All rights reserved. * *************************************************************************************************/ package nl.sense_os.service; import nl.sense_os.service.constants.SensePrefs; import nl.sense_os.service.constants.SensePrefs.Main; import nl.sense_os.service.scheduler.Scheduler; import android.annotation.TargetApi; import android.app.AlarmManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.TrafficStats; import android.os.BatteryManager; import android.os.Build; import android.os.SystemClock; import android.telephony.TelephonyManager; import android.util.Log; /** * This class is responsible for initiating the transmission of buffered data. It works by * registering the transmission task to the scheduler. The Sense service calls * {@link #scheduleTransmissions(Context)} when it starts sensing. <br/> * <br/> * When the transmission task is executed, an Intent is sent to the {@link MsgHandler} to empty its * buffer.<br/> * <br/> * The transmission frequency is based on the {@link Main#SYNC_RATE} preference. When the sync rate * is set to the real-time setting, we look at the and {@link Main#SAMPLE_RATE} to determine * periodic "just in case" transmissions. In case of transmission over 3G we transmit every one hour * for energy conservation. * * @author Steven Mulder <steven@sense-os.nl> */ public class DataTransmitter implements Runnable { private static final long ADAPTIVE_TX_INTERVAL = AlarmManager.INTERVAL_HALF_HOUR; private static final String TAG = "DataTransmitter"; private static DataTransmitter sInstance; /** * Factory method to get the singleton instance. * * @param context * @return instance */ public static DataTransmitter getInstance(Context context) { if (sInstance == null) { sInstance = new DataTransmitter(context); } return sInstance; } private Context mContext; private long mLastTxBytes = 0; private long mLastTxTime = 0; private long mTxInterval; private long mTxBytes; /** * Constructor. * * @param context * @see #getInstance(Context) */ @TargetApi(Build.VERSION_CODES.FROYO) protected DataTransmitter(Context context) { mContext = context; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { mTxBytes = TrafficStats.getMobileTxBytes(); } } @TargetApi(Build.VERSION_CODES.FROYO) @Override public void run() { // check if the service is (supposed to be) alive before scheduling next alarm if (true == ServiceStateHelper.getInstance(mContext).isLoggedIn()) { // check if transmission should be started ConnectivityManager connManager = (ConnectivityManager) mContext .getSystemService(Context.CONNECTIVITY_SERVICE); //Check battery state SharedPreferences mainPrefs = mContext.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE); //boolean isChargingOnly = mainPrefs.getBoolean(Advanced.IS_CHARGING_ONLY, true); String batteryState = mainPrefs.getString(SensePrefs.Main.Advanced.BATTERY, "When battery level above threshold"); if (0 == batteryState.compareTo("Only when charging")){ if(!isCharging()){ //if upload should be done only when the device is charging, return Log.d(TAG, "device is not charging, transfer delayed"); return; } } else if (0 == batteryState.compareTo("When battery level above threshold")){ String batteryTshold = mainPrefs.getString(SensePrefs.Main.Advanced.BATTERY_THRESHOLD, "60"); if (Integer.parseInt(batteryTshold) >= getBatteryLevel()){ Log.d(TAG, "device is low on battery, transfer delayed"); return; } } else if (0 == batteryState.compareTo("Always")){ Log.d(TAG, "Battery state: Always, continue the transfer"); } //Check mobile network NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); NetworkInfo mobile = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); String mobileNetwork = mainPrefs.getString(SensePrefs.Main.Advanced.MOBILE_NETWORK_UPLOAD, "WiFi"); if ((0 == mobileNetwork.compareTo("Any") && (wifi.isConnected() || mobile.isConnected()) )|| (0 == mobileNetwork.compareTo("WiFi") && wifi.isConnected()) || ((0 == mobileNetwork.compareTo("3G") || 0 == mobileNetwork.compareTo("4G") || 0 == mobileNetwork.compareTo("2G") )&& mobile.isConnected())){ Log.d(TAG, "Starting transmission"); // start the transmission if we have WiFi connection if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) || wifi.isConnected()) { if ((SystemClock.elapsedRealtime() - mLastTxTime >= mTxInterval)) { transmissionService(); } } else { // if there is no WiFi connection, postpone the transmission mLastTxBytes = TrafficStats.getMobileTxBytes() - mTxBytes; mTxBytes = TrafficStats.getMobileTxBytes(); if ((SystemClock.elapsedRealtime() - mLastTxTime >= ADAPTIVE_TX_INTERVAL)) { transmissionService(); } // if any transmission took place recently, use the tail to transmit the sensor data else if ((mLastTxBytes >= 500) && (SystemClock.elapsedRealtime() - mLastTxTime >= ADAPTIVE_TX_INTERVAL - (long) (ADAPTIVE_TX_INTERVAL * 0.2))) { transmissionService(); } else { // do nothing } } } else { Log.d(TAG, "Mobile network invalid, transfer delayed"); return; } } else { Log.d(TAG, "skip transmission: Sense service is not logged in"); // skip transmission: Sense service is not logged in } } public boolean isCharging(){ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = mContext.registerReceiver(null, ifilter); // Are we charging / charged? int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; return isCharging; } public float getBatteryLevel(){ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = mContext.registerReceiver(null, ifilter); int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); // int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); float batteryPct = level /*/ (float)scale*/; Log.d(TAG, "battery level: " + batteryPct); return batteryPct; } public int prefetchCacheSize(){ ///????????change sync rate better int DEFAULT_BATCH_SIZE = 10; int MAX_BATCH_SIZE = 100; ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); int PrefetchCacheSize = DEFAULT_BATCH_SIZE; switch (activeNetwork.getType()) { case (ConnectivityManager.TYPE_WIFI): PrefetchCacheSize = MAX_BATCH_SIZE; break; case (ConnectivityManager.TYPE_MOBILE): { switch (tm.getNetworkType()) { case (TelephonyManager.NETWORK_TYPE_LTE): //4g return PrefetchCacheSize * 2; case TelephonyManager.NETWORK_TYPE_UMTS: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_HSPA: case TelephonyManager.NETWORK_TYPE_EVDO_B: case TelephonyManager.NETWORK_TYPE_EHRPD: case TelephonyManager.NETWORK_TYPE_HSPAP: //3g return PrefetchCacheSize; case TelephonyManager.NETWORK_TYPE_GPRS: case TelephonyManager.NETWORK_TYPE_EDGE: case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_1xRTT: case TelephonyManager.NETWORK_TYPE_IDEN: //2g return PrefetchCacheSize / 2; default: break; } break; } default: break; } return PrefetchCacheSize; } /** * Starts the periodic transmission of sensor data. * * @param mContext * Context to access Scheduler */ public void startTransmissions(long transmissionInterval, long taskTransmitterInterval) { // schedule transmissions mTxInterval = transmissionInterval; Scheduler.getInstance(mContext).register(this, taskTransmitterInterval, (long) (taskTransmitterInterval * 0.2)); } /** * Stops the periodic transmission of sensor data. */ public void stopTransmissions() { // stop transmissions Scheduler.getInstance(mContext).unregister(this); } /** * Initiates the data transmission. */ public void transmissionService() { Log.v(TAG, "Start transmission"); Intent task = new Intent(mContext, MsgHandler.class); mLastTxTime = SystemClock.elapsedRealtime(); ComponentName service = mContext.startService(task); if (null == service) { Log.w(TAG, "Failed to start data sync service"); } } }